/*
 * Copyright (C) 2009 Plausible Labs Cooperative, Inc.
 * Copyright (c) 2009 Remy Demarest
 * All rights reserved.
 *
 * Permission is hereby granted, free of charge,
 * to any person obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to permit
 * persons to whom the Software is furnished to do so, subject to the following
 * conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#if TARGET_OS_IPHONE
#import "GTMSenTestCase.h"
#else
#import <SenTestingKit/SenTestingKit.h>
#endif

#import "Block.h"

/* An object used to track _PLBlock_object_assign/_PLBlock_object_dispose reference handling */
@interface PLReferenceCountObject : NSObject {
    void (^_onRelease)();
    void (^_onDealloc)();
}
@end

@implementation PLReferenceCountObject
- (id) initWithReleaseBlock: (void (^)()) onRelease deallocBlock: (void (^)()) onDealloc {
    if ((self = [super init]) == nil)
        return nil;

    _onRelease = [onRelease retain];
    _onDealloc = [onDealloc retain];

    return self;
}

- (int) doSomething {
    return 42;
}

- (void) release {
    _onRelease();
    [super release];
}

- (void) dealloc {
    _onDealloc();
    [_onRelease release];
    [_onDealloc release];
    [super dealloc];
}

@end

@interface BlockTests : SenTestCase @end

@implementation BlockTests

/* Block Objective-C reference counting */
- (void) testBlockObjCReferenceCounting {
    __block BOOL releaseCalled = NO;
    __block BOOL deallocCalled = NO;

    /* Create the release tracking block */
    PLReferenceCountObject *obj = [[PLReferenceCountObject alloc] initWithReleaseBlock: ^{
        releaseCalled = YES;
    } deallocBlock: ^{
        deallocCalled = YES;
    }];
    
    /* Capture the refcount tracker in a copied block */
    STAssertEquals([obj doSomething], 42, @"Method did not return 42");
    void (^b)() = Block_copy(^{
        STAssertEquals([obj doSomething], 42, @"Method did not return 42");
    });

    /* Execute the block */
    b();

    /* Release the object, and the block that has captured it*/
    STAssertFalse(releaseCalled, @"The block-retained object has already been released");
    Block_release(b);
    STAssertTrue(releaseCalled, @"The block-retained object has not been released");
    STAssertFalse(deallocCalled, @"The block-retained object has already been dealloced");
    [obj release];
    STAssertTrue(deallocCalled, @"The block-retained object was not released");
}

/* Block Objective-C casting */
- (void) testBlockObjC {
    NSString *value = @"String";
    
    NSString *(^b)() = ^{
        return value;
    };
    
    NSString *(^copy)() = [b copy];
    STAssertEquals(@"String", copy(), @"Block did not return expected value.");
    [copy autorelease];
}

/* A block copy */
- (void) testBlockCopy {
    NSString *value = @"String";
    
    NSString *(^b)() = ^{
        return value;
    };

    NSString *(^copy)() = Block_copy(b);
    STAssertEquals(@"String", copy(), @"Block did not return expected value.");
    Block_release(copy);
}

/* A block capturing Objective-C types */
- (void) testObjectCapture {
    NSString *value = @"String";
    
    NSString *(^b)() = ^{
        return value;
    };
    
    STAssertEquals(@"String", b(), @"Block did not return expected value.");
}

/* A block capturing primitive variable types */
- (void) testPrimitiveCapture {
    int value = 5;

    int (^b)() = ^{
        return value;
    };

    STAssertEquals(5, b(), @"Block did not return expected value.");
}

/* Simple block, capturing no variables */
- (void) testNoCapture {
    int (^b)(int) = ^(int v){ 
        return v; 
    };
    STAssertEquals(3, b(3), @"Block did not return expected value.");
}


@end
